package lbs

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// QQLbsClient QQ 地图
type QQLbsClient struct {
	// 应用key
	appKey string
	// 签名 key
	appSecret string
	// enable sign
	enableSign bool
	// uri
	uri string
}

// NewQQLbsClient QQ 地图
func NewQQLbsClient(appKey, appSecret string) *QQLbsClient {
	enableSign := appSecret != ""
	return &QQLbsClient{
		appKey:     appKey,
		appSecret:  appSecret,
		enableSign: enableSign,
		uri:        "https://apis.map.qq.com",
	}
}

func (q *QQLbsClient) buildQuery(params map[string]interface{}) url.Values {
	query := url.Values{}
	for key, val := range params {
		tmp := q.fieldToString(val)
		if tmp != "" {
			query.Add(key, tmp)
		}
	}
	return query
}

func (q *QQLbsClient) getUri(api string, query url.Values) string {
	return fmt.Sprintf("%s%s?%s", q.uri, api, query.Encode())
}

func (q *QQLbsClient) doRequest(ctx context.Context, method, api string, param map[string]interface{}, retry int) ([]byte, error) {
	// 公共参数
	param["key"] = q.appKey
	param["output"] = "json"
	query := url.Values{}
	var reqBody []byte
	// 分别处理 get post
	if strings.ToUpper(method) == "GET" {
		query = q.buildQuery(param)
	} else {
		reqBody, _ = json.Marshal(param)
	}
	// 开启签名
	if q.enableSign {
		query.Add("sig", q.Sign(api, q.appSecret, param))
	}
	var resp *http.Response
	client := &http.Client{Timeout: 20 * time.Second}
	uri := q.getUri(api, query)
	for {
		if retry <= 0 {
			break
		}
		req, err := http.NewRequestWithContext(ctx, method, uri, bytes.NewBuffer(reqBody))
		if err != nil {
			return nil, err
		}
		resp, err = client.Do(req)
		if err != nil || resp.StatusCode == http.StatusInternalServerError {
			time.Sleep(200 * time.Millisecond)
			retry--
		} else {
			break
		}
	}
	return q._response(resp)
}

func (q *QQLbsClient) _response(resp *http.Response) ([]byte, error) {
	if resp == nil {
		return nil, errors.New("返回结果为空")
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	res := struct {
		Status  int    `json:"status"`
		Message string `json:"message"`
	}{}
	err = json.Unmarshal(body, &res)
	if err != nil {
		return nil, err
	}
	if res.Status != 0 {
		return nil, fmt.Errorf("%s", res.Message)
	}
	return body, nil
}

// PlaceSearch 地址检索
func (q *QQLbsClient) PlaceSearch(ctx context.Context, p *PlaceSearchReq) (*PlaceSearchReply, error) {
	api := "/ws/place/v1/search"
	if p == nil {
		return nil, nil
	}
	if p.PageSize == 0 {
		p.PageSize = 10
	}
	if p.PageIndex == 0 {
		p.PageIndex = 1
	}
	if p.Boundary != nil {
		return nil, errors.New("范围必填")
	}
	param := map[string]interface{}{
		"keyword":    p.Keyword,
		"boundary":   p.Boundary.Format(),
		"filter":     p.Filter,
		"orderby":    "_distance",
		"page_size":  p.PageSize,
		"page_index": p.PageIndex,
	}
	body, err := q.doRequest(ctx, "GET", api, param, 2)
	if err != nil {
		return nil, err
	}
	res := &PlaceSearchReply{}
	err = json.Unmarshal(body, res)
	return res, err
}

// GetAddressByLocation 逆地址解析
func (q *QQLbsClient) GetAddressByLocation(ctx context.Context, p *GeoAddressCoderReq) (*GeoAddressCoderReply, error) {
	api := "/ws/geocoder/v1"
	if p == nil {
		return nil, nil
	}
	if p.Location == nil {
		return nil, errors.New("坐标必填")
	}
	getPoi := 0
	if p.GetPoi {
		getPoi = 1
	}
	param := map[string]interface{}{
		"location":    p.Location.Format(),
		"get_poi":     getPoi,
		"poi_options": p.PoiPotions,
	}
	body, err := q.doRequest(ctx, "GET", api, param, 2)
	if err != nil {
		return nil, err
	}
	res := &GeoAddressCoderReply{}
	err = json.Unmarshal(body, res)
	return res, err
}

// GetLocationByAddress 地址解析
func (q *QQLbsClient) GetLocationByAddress(ctx context.Context, p *GeoLocationCoderReq) (*GeoLocationCoderReply, error) {
	api := "/ws/geocoder/v1"
	if p == nil {
		return nil, nil
	}
	param := map[string]interface{}{
		"address": p.Address,
		"region":  p.Region,
	}
	body, err := q.doRequest(ctx, "GET", api, param, 2)
	if err != nil {
		return nil, err
	}
	res := &GeoLocationCoderReply{}
	err = json.Unmarshal(body, res)
	return res, err
}

// DistanceMatrix 批量距离计算
func (q *QQLbsClient) DistanceMatrix(ctx context.Context, p *DistanceMatrixReq) (*DistanceMatrixReply, error) {
	api := "/ws/distance/v1/matrix"
	if p == nil {
		return nil, nil
	}
	if p.From == nil || p.To == nil {
		return nil, InvalidLocation
	}
	mode := ""
	if p.Mode != "" {
		switch p.Mode {
		case "driving":
			mode = "driving"
		case "walking":
			mode = "walking"
		case "bicycling":
			mode = "bicycling"
		default:
			mode = "walking"
		}
	}
	param := map[string]interface{}{
		"mode": mode,
		"from": p.From.Format(),
		"to":   p.To.Format(),
	}
	body, err := q.doRequest(ctx, "GET", api, param, 2)
	if err != nil {
		return nil, err
	}
	res := &DistanceMatrixReply{}
	if err = json.Unmarshal(body, res); err != nil {
		return nil, err
	}
	res.from = p.From
	res.to = p.To
	return res, nil
}

// LocationTranslate 坐标转换
func (q *QQLbsClient) LocationTranslate(ctx context.Context, p *LocationsTranslateReq) (*LocationsTranslateReply, error) {
	api := "/ws/coord/v1/translate"
	if p == nil {
		return nil, nil
	}
	if p.Locations == nil {
		return nil, nil
	}
	TType := 5
	switch p.Type {
	case 1, 2, 3, 4, 6:
		TType = p.Type
	default:
		TType = 5
	}
	param := map[string]interface{}{
		"locations": p.Locations.Format(),
		"type":      TType,
	}
	body, err := q.doRequest(ctx, "GET", api, param, 2)
	if err != nil {
		return nil, err
	}
	res := &LocationsTranslateReply{}
	if err = json.Unmarshal(body, res); err != nil {
		return nil, err
	}
	res.origins = p.Locations
	return res, nil
}

// IPLocation ip 定位
func (q *QQLbsClient) IPLocation(ctx context.Context, ip string) (*IPLocationReply, error) {
	api := "/ws/location/v1/ip"
	if ip == "" {
		return nil, nil
	}
	param := map[string]interface{}{
		"ip": ip,
	}
	body, err := q.doRequest(ctx, "GET", api, param, 2)
	if err != nil {
		return nil, err
	}
	res := &IPLocationReply{}
	err = json.Unmarshal(body, res)
	return res, err
}
